/**********************************************************************
*  Copyright (c) 2007 - Victor Jacobs - victor.jacobs@gmail.com
*
*  Permission is hereby granted, free of charge, to any person
*  obtaining a copy of this software and associated documentation
*  files (the "Software"), to deal in the Software without
*  restriction, including without limitation the rights to use,
*  copy, modify, merge, publish, distribute, sublicense, and/or sell
*  copies of the Software, and to permit persons to whom the
*  Software is furnished to do so, subject to the following
*  conditions:
*
*  The above copyright notice and this permission notice shall be
*  included in all copies or substantial portions of the Software.
*
*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
*  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
*  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
*  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
*  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
*  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
*  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
*  OTHER DEALINGS IN THE SOFTWARE.
**********************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using Vj.MathLibrary;
using Vj.ShapeLibrary;
using System.Drawing;

namespace Vj.BoidLibrary
{
    public class Flock
    {
        #region Data members
        private Boid[] boidList;
        private int numOfBoids;
        private Color flockColor;
        private double distanceFactor = 5.0;
        private double centerOfVelFactor = 3.0;
        private double centerOfMassFactor = 10.0;
        private bool drawVelocity;
        private bool drawCenter2Floor;
        private bool drawCenter2Origin;

        #endregion

        #region Properties

        public int NumOfBoids
        {
            get { return numOfBoids; }
            set { numOfBoids = value; }
        }

        public Color FlockColor
        {
            get { return flockColor; }
            set { flockColor = value; }
        }

        private Vector centerLocation;

        public Vector CenterLocation
        {
            get { return centerLocation; }
            set { centerLocation = value; }
        }


        public double DistanceFactor
        {
            get { return distanceFactor; }
            set { distanceFactor = value; }
        }

        public double CenterOfVelFactor
        {
            get { return centerOfVelFactor; }
            set { centerOfVelFactor = value; }
        }

        public double CenterOfMassFactor
        {
            get { return centerOfMassFactor; }
            set { centerOfMassFactor = value; }
        }

        public bool DrawVelocity
        {
            get { return drawVelocity; }
            set { drawVelocity = value; }
        }

        public bool DrawCenter2Floor
        {
            get { return drawCenter2Floor; }
            set { drawCenter2Floor = value; }
        }

        public bool DrawCenter2Origin
        {
            get { return drawCenter2Origin; }
            set { drawCenter2Origin = value; }
        }
	
	
	

        #endregion

        public Flock(Color color)
        {
            numOfBoids = 10;
            this.flockColor = color;

            boidList = new Boid[numOfBoids];

            for (int i = 0; i < boidList.Length; i++)
            {
                boidList[i] = new Boid(this.flockColor);
                boidList[i].Color = color;
                boidList[i].Velocity = new Vector(RandomGrabber.Next(-1.0, 1.0), RandomGrabber.Next(-1.0, 1.0), RandomGrabber.Next(-1.0, 1.0));
                boidList[i].Position = new Vector(RandomGrabber.Next(-10.0, 10.0), RandomGrabber.Next(-10.0, 10.0), RandomGrabber.Next(-10.0, 10.0));
            }
        }

        public void Tick()
        {
            DoStep();
            Draw();
            centerLocation = new Vector(0.0, 0.0, 0.0);
        }

        public void Draw()
        {

            for (int i = 0; i < boidList.Length; i++)
            {
                if (drawVelocity)
                {
                    Ray.Draw(boidList[i].Position, boidList[i].Position + boidList[i].Velocity);
                }

                boidList[i].Draw();
            }

            if(drawCenter2Floor)
                Ray.Draw(new Vector(centerLocation.X, -20.0, centerLocation.Z), centerLocation, new Vector(1.0, 1.0, 1.0)); 
            if(drawCenter2Origin)
                Ray.Draw(new Vector(0.0, 0.0, 0.0), centerLocation, new Vector(1.0, 1.0, 1.0)); 

        }

        public void Reset()
        {
            boidList = new Boid[numOfBoids];

            for (int i = 0; i < boidList.Length; i++)
            {
                boidList[i] = new Boid(this.flockColor);

                boidList[i].Velocity = new Vector(RandomGrabber.Next(-1.0, 1.0), RandomGrabber.Next(-1.0, 1.0), RandomGrabber.Next(-1.0, 1.0));
                boidList[i].Position = new Vector(RandomGrabber.Next(-10.0 * RandomGrabber.Next(0.0, 3.0), 10.0), RandomGrabber.Next(-10.0 * RandomGrabber.Next(0.0, 3.0), 10.0), RandomGrabber.Next(-10.0 * RandomGrabber.Next(0.0, 3.0), 10.0));
            }
        }

        private void DoStep()
        {
            Vector sumVelocity = new Vector(3);

            for (int i = 0; i < boidList.Length; i++)
            {
                Vector v1 = rule1(i);
                Vector v2 = rule2(i);
                Vector v3 = rule3(i);
                Vector v4 = rule4(i);
                //Vector v5 = rule5(i);

                sumVelocity = v1 + v2 + v3 + v4;

                boidList[i].Velocity = sumVelocity;//.Normalize();

                boidList[i].Position = boidList[i].Position + (boidList[i].Velocity * 0.5);
            }

        }

        // calculate tendency toward center of all other boids
        private Vector rule1(int thisBoid)
        {
            Vector centerPos = new Vector(3);

            for (int otherBoid = 0; otherBoid < boidList.Length; otherBoid++)
            {
                if (thisBoid != otherBoid)
                {
                    centerPos = centerPos + boidList[otherBoid].Position;
                }
            }

            centerPos = centerPos / (boidList.Length - 1);

            centerLocation = centerPos;

            Vector centerTendency = (centerPos - boidList[thisBoid].Position) / centerOfMassFactor;

            return centerTendency;
        }

        // maintain distance from surrounding boids
        private Vector rule2(int thisBoid)
        {
            Vector steerAway = new Vector(3);

            for (int otherBoid = 0; otherBoid < boidList.Length; otherBoid++)
            {
                if (thisBoid != otherBoid)
                {
                    double distance = Vector.Magnitude(boidList[thisBoid].Position - boidList[otherBoid].Position);

                    if (distance < distanceFactor)
                    {
                        steerAway = steerAway - (boidList[thisBoid].Position - boidList[otherBoid].Position);
                    }
                }
            }

            //steerAway.normalize();
            return steerAway;
        }

        // tend velocity to average of other boids velocities
        private Vector rule3(int thisBoid)
        {

            Vector centerVel = new Vector(3);

            for (int otherBoid = 0; otherBoid < boidList.Length; otherBoid++)
            {
                if (thisBoid != otherBoid)
                {
                    centerVel = centerVel + boidList[otherBoid].Velocity;
                }
            }

            centerVel = centerVel / (float)(boidList.Length - 1);
            Vector centerTendency = (centerVel - boidList[thisBoid].Velocity) / centerOfVelFactor;

            return centerTendency;
        }

        // calculate tendency toward giving location
        private Vector rule4(int thisBoid)
        {
            Vector tendency = new Vector(0.0, 4.0, 0.0);
            return (tendency - boidList[thisBoid].Position) / 100.0;

        }

        private Vector rule5(int thisBoid)
        {
            return new Vector(0.0, 0.0, 0.0);
        }

        public override string ToString()
        {
            string output = "";
            for (int i = 0; i < boidList.Length; i++)
            {
                output += "Boid [" + i + "] \r\n";
                output += boidList[i].ToString();
            }
            return output;
        }
    }
}